package ru.myrtle.umf.codegen;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class GenLog implements GenMonitor {

    private static final String CONTEXT_ID_ROOT = "ROOT";
    private static final String CONTEXT_ID_GENERATOR = "GENERATOR";
    private static final String CONTEXT_ID_RULE = "RULE";
    private static final String CONTEXT_ID_DOMAIN = "DOMAIN";
    private static final String CONTEXT_ID_TEMPLATE = "TEMPLATE";

    /*
     *
     */

    private interface LogElement {

        public void visit(LogVisitor visitor);
    }

    /*
     *
     */

    private static class LogContext implements LogElement {

        protected final String name;
        protected final List<LogElement> elements =
                new ArrayList<LogElement>();

        public LogContext(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void addMessage(LogMessage message) {
            elements.add(message);
        }

        public LogContext subcontext(String name) {
            LogContext context = new LogContext(name);

            elements.add(context);

            return context;
        }

        @Override
        public void visit(LogVisitor visitor) {
            visitor.onContextStart(this);

            for (LogElement element : elements) {
                element.visit(visitor);
            }

            visitor.onContextEnd(this);
        }
    }

    /*
     *
     */

    private static class LogMessage implements LogElement {

        private final String contents;

        public LogMessage(String contents) {
            this.contents = contents;
        }

        public String getContents() {
            return contents;
        }

        @Override
        public void visit(LogVisitor visitor) {
            visitor.onMessage(this);
        }
    }

    /*
     *
     */

    private static class LogRoot extends LogContext {

        public LogRoot(String name) {
            super(name);
        }

        @Override
        public void visit(LogVisitor visitor) {
            for (LogElement element : elements) {
                element.visit(visitor);
            }
        }
    }

    /*
     *
     */

    private interface LogVisitor {

        public void onContextStart(LogContext context);
        public void onContextEnd(LogContext context);

        public void onMessage(LogMessage message);
    }

    /*
     *
     */

    private static class Printer implements LogVisitor {

        private static final int DEFAULT_INDENT = 0;
        private static final int DEFAULT_INDENT_STEP = 4;

        private static final String STR_EMPTY = "";
        private static final String STR_SPACE = " ";

        private final PrintWriter out;

        private int indent;
        private int indentStep;

        /*
         *
         */

        public Printer(PrintWriter out) {
            this(out, DEFAULT_INDENT, DEFAULT_INDENT_STEP);
        }

        public Printer(PrintWriter out, int indent, int indentStep) {
            this.out = out;

            this.indent = indent;
            this.indentStep = indentStep;
        }

        /*
         *
         */

        private static String replicate(int numberOfTimes, String string) {
            if (numberOfTimes <= 0) {
                return STR_EMPTY;
            }

            StringBuffer result = new StringBuffer(numberOfTimes);

            for (int i = 0; i < numberOfTimes; i++) {
                result.append(string);
            }

            return result.toString();
        }

        private void indent() {
            indent += indentStep;
        }

        private void unindent() {
            indent -= indentStep;

            if (indent < 0) {
                indent = 0;
            }
        }

        private void println(String text) {
            out.println(
                    replicate(indent, STR_SPACE) + text
            );
            out.flush();
        }

        /*
         *
         */

        @Override
        public void onContextStart(LogContext context) {
            indent();
        }

        @Override
        public void onContextEnd(LogContext context) {
            unindent();
        }

        @Override
        public void onMessage(LogMessage message) {
            println(
                    message.getContents()
            );
        }

        /*
         *
         */

        public void print(LogContext context) {
            context.visit(this);
        }
    }

    /*
     *
     */

    private final LogContext root = new LogRoot(CONTEXT_ID_ROOT);
    private final Stack<LogContext> contexts =
            new Stack<LogContext>();

    private LogContext currentContext() {
        return contexts.peek();
    }

    private void addMessage(LogMessage message) {
        currentContext().addMessage(message);
    }

    private void startContext(String name) {
        LogContext current = currentContext();
        LogContext nested = current.subcontext(name);

        contexts.push(nested);
    }

    private void closeContext() {
        contexts.pop();
    }

    /*
     *
     */

    public GenLog() {
        contexts.push(root);
    }

    /*
     *
     */

    @Override
    public void modelStarted(String modelName, String destination, String suffix) {
        startContext(CONTEXT_ID_GENERATOR);
        addMessage(
                new LogMessage(
                        "Model " + modelName + " started with destination " + destination + ", suffix " + suffix
                )
        );
    }

    @Override
    public void modelFinished() {
        closeContext();
    }

    @Override
    public void ruleStarted(String ruleName) {
        startContext(CONTEXT_ID_RULE);
        addMessage(
                new LogMessage(
                        "Runnting rule " + ruleName
                )
        );
    }

    @Override
    public void ruleObject(Object object) {
        addMessage(
                new LogMessage(
                        "Processing object " + object
                )
        );
    }

    @Override
    public void ruleFinished() {
        closeContext();
    }

    @Override
    public void domainStarted(String domainName) {
        startContext(CONTEXT_ID_DOMAIN);
        addMessage(
                new LogMessage(
                        "Running domain " + domainName
                )
        );
    }

    @Override
    public void domainFinished() {
        closeContext();
    }

    @Override
    public void templateStarted(String templateName) {
        startContext(CONTEXT_ID_TEMPLATE);
        addMessage(
                new LogMessage(
                        "Running template " + templateName
                )
        );
    }

    @Override
    public void templatePerformed(String result) {
        addMessage(
                new LogMessage(
                        "Template result written to " + result
                )
        );
        closeContext();
    }

    @Override
    public void templateRejected() {
        addMessage(
                new LogMessage(
                        "Template rejected"
                )
        );
        closeContext();
    }

    /*
     *
     */

    public void print() {
        print(System.out);
    }

    public void print(PrintStream stream) {
        print(
                new PrintWriter(stream)
        );
    }

    public void print(PrintWriter out) {
        Printer printer = new Printer(out);
        printer.print(root);
    }
}
